package com.college.common.utils.security;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
import java.util.function.Function;

/**
 * Created by nieqing
 */
public class XxhSignUtil {
    public static final String CHARSET                   = "UTF-8";
    public static final String RSA_ALGORITHM             = "RSA";
    public static final String RSA_ALGORITHM_SIGN_SHA1   = "SHA1WithRSA";
    public static final String RSA_ALGORITHM_SIGN_SHA256 = "SHA256withRSA";

    public static String doSign(Map<String, Object> map, String privateKeyStr) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            // 通过X509编码的Key指令获得公钥对象
            // X509EncodedKeySpec x509KeySpec = new
            // X509EncodedKeySpec(Base64.decodeBase64(publicKey));
            // this.publicKey = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
            // 通过PKCS#8编码的Key指令获得私钥对象
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyStr));
            RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);

            String data = getSignContent(map);
            Signature signature = Signature.getInstance(RSA_ALGORITHM_SIGN_SHA256);
            signature.initSign(privateKey);
            signature.update(data.getBytes(CHARSET));
            return Base64.encodeBase64String(signature.sign());
        } catch (Exception e) {
            throw new RuntimeException("签名失败", e);
        }
    }

    public static String doSignWithNoOrder(String content, String privateKeyStr) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            // 通过X509编码的Key指令获得公钥对象
            // X509EncodedKeySpec x509KeySpec = new
            // X509EncodedKeySpec(Base64.decodeBase64(publicKey));
            // this.publicKey = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
            // 通过PKCS#8编码的Key指令获得私钥对象
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(
                    Base64.decodeBase64(privateKeyStr));
            RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);

            Signature signature = Signature.getInstance(RSA_ALGORITHM_SIGN_SHA256);
            signature.initSign(privateKey);
            signature.update(content.getBytes());
            return new String(Base64.encodeBase64(signature.sign()));
        } catch (Exception e) {
            throw new RuntimeException("签名失败", e);
        }
    }

    /**
     * 参数格式转换Map转String
     *
     * @param sortedParams
     * @return
     */
    public static String getSignContent(Map<String, Object> sortedParams) {
        StringBuffer content = new StringBuffer();
        List<String> keys = new ArrayList<String>(sortedParams.keySet());
        Collections.sort(keys);
        int index = 0;
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = sortedParams.get(key).toString();
            if (isNotEmpty(key) && isNotEmpty(value)) {
                content.append((index == 0 ? "" : "&") + key + "=" + value);
                index++;
            }
        }
        return content.toString();
    }

    private static boolean isNotEmpty(String str) {
        return ((str != null) && (str.length() > 0));
    }

    private static boolean isEmpty(String str) {
        return ((str == null) || (str.length() == 0));
    }

    /**
     * 私钥加密
     *
     * @param map 待加密数据
     * @param privateKeyStr 密钥
     * @return String 加密数据
     */
    public static String encryptByPrivateKey(Map<String, Object> map, String privateKeyStr) throws Exception {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            // 通过PKCS#8编码的Key指令获得私钥对象
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyStr));
            RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
            // 数据加密
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);

            // 待加密内容
            String content = getSignContent(map);

            byte[] data = cipher.doFinal(content.getBytes(CHARSET));
            return Base64.encodeBase64String(data);
        } catch (Exception e) {
            throw new RuntimeException("私钥加密失败", e);
        }
    }

    /**
     * 公钥加密
     *
     * @param map 待加密数据
     * @param publicKeyStr 密钥
     * @return String 加密数据
     */
    public static String encryptByPublicKey(Map<String, Object> map, String publicKeyStr) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            // 通过X509编码的Key指令获得公钥对象
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyStr));
            RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);

            // 数据加密
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);

            // 待加密内容
            String content = getSignContent(map);

            byte[] data = cipher.doFinal(content.getBytes(CHARSET));
            return Base64.encodeBase64String(data);
        } catch (Exception e) {
            throw new RuntimeException("公钥加密失败", e);
        }
    }

    /**
     * 公钥加密(无排序)
     *
     * @param content 待加密数据
     * @param publicKeyStr 密钥
     * @return String 加密数据
     */
    public static String encryptByPublicKeyWithNoOrderForLongData(String content, String publicKeyStr) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            // 通过X509编码的Key指令获得公钥对象
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(
                    Base64.decodeBase64(publicKeyStr));
            RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);

            // 数据加密
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);

            byte[] data = content.getBytes();
            int inputLen = data.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > 117) {
                    cache = cipher.doFinal(data, offSet, 117);
                } else {
                    cache = cipher.doFinal(data, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * 117;
            }
            byte[] encryptedData = out.toByteArray();
            out.close();

            return new String(org.bouncycastle.util.encoders.Base64.encode(encryptedData));
        } catch (Exception e) {
            throw new RuntimeException("公钥加密失败", e);
        }
    }

    /**
     * RSA-私钥解密过程
     *
     * @param context
     * @param privateKeyStr
     * @return
     * @throws Exception
     */
    public static String decryptRSA(String context, String privateKeyStr) {
        if (isEmpty(privateKeyStr)) {
            throw new RuntimeException("解密私钥为空, 请设置");
        }
        Cipher cipher;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            // 通过PKCS#8编码的Key指令获得私钥对象
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyStr));
            RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);

            // 使用默认RSA
            cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] output = cipher.doFinal(Base64.decodeBase64(context));
            return new String(output);
        } catch (Exception e) {
            throw new RuntimeException("解密数据失败");
        }
    }

    /**
     * RSA-私钥解密过程(长度超128byte分段解密)
     *
     * @param context
     * @param privateKeyStr
     * @return
     * @throws Exception
     */
    public static String decryptRSAByPrivateKeyForLongData(String context, String privateKeyStr) {
        if (isEmpty(privateKeyStr)) {
            throw new RuntimeException("解密私钥为空, 请设置");
        }
        Cipher cipher;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            // 通过PKCS#8编码的Key指令获得私钥对象
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyStr));
            RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);

            // 使用默认RSA
            cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, privateKey);

            byte[] encryptedData = Base64.decodeBase64(context);
            int inputLen = encryptedData.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段解密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > 128) {
                    cache = cipher.doFinal(encryptedData, offSet, 128);
                } else {
                    cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * 128;
            }
            byte[] decryptedData = out.toByteArray();
            out.close();
            return new String(decryptedData);
        } catch (Exception e) {
            throw new RuntimeException("解密数据失败");
        }
    }

    /**
     * 根据加签类型验证签名
     *
     * @param signParams
     * @param sign
     * @param publicKey
     * @return
     */
    public static boolean rsa256CheckContent(Map<String, Object> signParams, String sign, String publicKey) {
        try {
            String content = getSignContent(signParams);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            byte[] encodedKey = Base64.decodeBase64(publicKey);
            PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
            Signature signature = Signature.getInstance(RSA_ALGORITHM_SIGN_SHA256);

            signature.initVerify(pubKey);
            signature.update(content.getBytes(CHARSET));
            boolean bverify = signature.verify(Base64.decodeBase64(sign));
            return bverify;
        } catch (Exception e) {
            throw new RuntimeException("验签失败");
        }
    }

    /**
     * 根据加签类型验证签名
     *
     * @param content
     * @param sign
     * @param publicKey
     * @return
     */
    public static boolean rsa256CheckContentWithNoOrder(String content, String sign, String publicKey) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            byte[] encodedKey = Base64.decodeBase64(publicKey);
            PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
            Signature signature = Signature.getInstance(RSA_ALGORITHM_SIGN_SHA256);

            signature.initVerify(pubKey);
            signature.update(content.getBytes(CHARSET));
            boolean bverify = signature.verify(Base64.decodeBase64(sign));
            return bverify;
        } catch (Exception e) {
            throw new RuntimeException("验签失败");
        }
    }

    /**
     * RSA签名
     *
     * @param content 待签名的字符串
     * @param privateKey rsa私钥字符串
     * @return
     */
    public static String doRsaSign(String content, String privateKey) {
        return doRsaSign(content, privateKey, CHARSET);
    }

    /**
     * RSA签名
     *
     * @param content 待签名的字符串
     * @param privateKey rsa私钥字符串
     * @param charset 字符编码
     * @return
     */
    public static String doRsaSign(String content, String privateKey, String charset) {
        try {
            PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
            KeyFactory keyf = KeyFactory.getInstance("RSA");
            PrivateKey priKey = keyf.generatePrivate(priPKCS8);

            Signature signature = Signature.getInstance(RSA_ALGORITHM_SIGN_SHA1);
            signature.initSign(priKey);
            if (StringUtils.isEmpty(charset)) {
                signature.update(content.getBytes());
            } else {
                signature.update(content.getBytes(charset));
            }
            byte[] signed = signature.sign();
            return new String(Base64.encodeBase64(signed));
        } catch (Exception e) {
            throw new RuntimeException("签名失败", e);
        }
    }

    /**
     * @param content 待签名数据
     * @param sign 签名值
     * @param publicKey 分配给开发商公钥
     * @return
     */
    public static boolean doRsaSignCheck(String content, String sign, String publicKey) {
        return doRsaSignCheck(content, sign, publicKey, CHARSET);
    }

    /**
     * RSA验签
     *
     * @param content 待签名数据
     * @param sign 签名值
     * @param publicKey 分配给开发商公钥
     * @param charset 字符集编码
     * @return
     */
    public static boolean doRsaSignCheck(String content, String sign, String publicKey, String charset) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            byte[] encodedKey = Base64.decodeBase64(publicKey);
            PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
            Signature signature = Signature.getInstance(RSA_ALGORITHM_SIGN_SHA1);
            signature.initVerify(pubKey);
            signature.update(content.getBytes(charset));
            return signature.verify(Base64.decodeBase64(sign));
        } catch (Exception e) {
            throw new RuntimeException("验签失败", e);
        }
    }

    public static String encryptRSAByPrivateKey(String content, String privateKey) {
        return doRSA(content, privateKey, KeyTypeEnum.PRIVATE_KEY, RSATypeEnum.ENCRYPT_MODE);
    }

    public static String encryptRSAByPublicKey(String content, String publicKey) {
        return doRSA(content, publicKey, KeyTypeEnum.PUBLIC_KEY, RSATypeEnum.ENCRYPT_MODE);
    }

    public static String decryptRSAByPrivateKey(String content, String privateKey) {
        return doRSA(content, privateKey, KeyTypeEnum.PRIVATE_KEY, RSATypeEnum.DECRYPT_MODE);
    }

    public static String decryptRSAByPublicKey(String content, String publicKey) {
        return doRSA(content, publicKey, KeyTypeEnum.PUBLIC_KEY, RSATypeEnum.DECRYPT_MODE);
    }

    private static String doRSA(String content, String key, KeyTypeEnum keyType, RSATypeEnum rsaType) {
        if (StringUtils.isEmpty(key)) {
            throw new RuntimeException(keyType.getDesc() + "为空, 请设置");
        }
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            // 数据加密
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(rsaType.getCipherMode(), keyType.getKeyFunction().apply(keyFactory, key));
            return rsaType.returnFunction.apply(cipher, content);
        } catch (Exception e) {
            throw new RuntimeException(rsaType.getErrMsg(), e);
        }
    }

    @FunctionalInterface
    public interface MyBiFunction<T, U, R> {
        R apply(T t, U u) throws Exception;

        default <V> MyBiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) throws Exception {
            Objects.requireNonNull(after);
            return (T t, U u) -> after.apply(apply(t, u));
        }
    }

    @Getter
    @AllArgsConstructor
    public enum KeyTypeEnum {
        PRIVATE_KEY(0, "私钥",
                (keyFactory, key) -> keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(key)))),
        PUBLIC_KEY(1, "公钥",
                (keyFactory, key) -> keyFactory.generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(key)))),

        ;

        private Integer                               code;
        private String                                desc;
        private MyBiFunction<KeyFactory, String, Key> keyFunction;
    }

    @Getter
    @AllArgsConstructor
    public enum RSATypeEnum {
        DECRYPT_MODE(0, "解密模式", Cipher.DECRYPT_MODE, "RSA解密失败",
                (cipher, txt) -> new String(cipher.doFinal(Base64.decodeBase64(txt)))),
        ENCRYPT_MODE(1, "加密模式", Cipher.ENCRYPT_MODE, "RSA加密失败",
                (cipher, txt) -> Base64.encodeBase64String(cipher.doFinal(txt.getBytes(CHARSET)))),

        ;

        private Integer                              code;
        private String                               desc;
        private int                                  cipherMode;
        private String                               errMsg;
        private MyBiFunction<Cipher, String, String> returnFunction;
    }
}
